
#include "dude.h"
#include "sprite_sheet.h"
#include "game_engine.h"
#include "particle.h"
#include "utility.h"
#include "SDL_gfx/SDL_rotozoom.h"
#include "battle_scene.h"
#include "SDL_mixer.h"
#include <sstream>
#include "progress_bar.h"

#define MOVEMENT_INCREMENT 5

Dude::Dude(DudeController &dudeController, PlayerSide playerSide, DudeTemplate &dudeTemplate)
	: controller(dudeController) //Initialize reference to my controller
{
	std::stringstream logLine;
	logLine << "Dude(): " << (playerSide == PLAYER_LEFT ? "Left" : "Right") << " Template: " << dudeTemplate.baseResourceName;
	log_info(logLine.str());
	node = NULL;
	appearance = DUDE_NORMAL;
	action = DUDE_NOTHING;
	//Copy info from template
	stats = dudeTemplate.stats;
	stats.life = stats.getLifeDefault();
	baseResourceName = dudeTemplate.baseResourceName;
	boundsImage = NULL;
	xDir = 1.0;
	side = playerSide;
	pathNumber = -1;
	//nextDude = NULL;
	//prevDude = NULL;
	isDying = false;

	//These keep track of when he last acted and when he can act again
	actionCounter = 0;
	actionTime = stats.getActionRate();
}

Dude::~Dude()
{
	log_info(std::string("~Dude(): resName:") + baseResourceName);
	if (node != NULL)
	{
		if (node->getParent() != NULL)
		{
			node->removeFromParent();
		}

		delete node;
		node = NULL;
	}

	if (boundsImage != NULL)
	{
		SDL_FreeSurface(boundsImage);
		boundsImage = NULL;
	}
}

/*
bool Dude::atFront()
{
	return nextDude == NULL;
}
*/

double Dude::getXOverlap(PD_rect &movingRect, PD_rect &other, double xDir)
{
	double x = 0;

	if (xDir > 0)
	{
		//If i'm on the left (or if my left and right are surrounding the left edge of the other rect
		//if (movingRect.left() < other.left() && movingRect.right() >= other.left())
		if (movingRect.right() > other.left())
		{
			return movingRect.right() - other.left();
		}
	}
	else
	{
		//If i'm on the right (if my left and right are surrounging the right edge of the other
		//if (movingRect.right() > other.right() && movingRect.left() <= other.right())
		if (movingRect.left() < other.right())
		{
			return other.right() - movingRect.left();
		}
	}
	return 0.0;
}

bool Dude::canMoveForward(double &amountCanMove)
{
	//double movement_amount = bounds.width() * 0.75;
	//Where middle boundary is (zero width)
	PD_rect middleBounds;
	middleBounds.setTopLeft(controller.GetMiddleBoundary(), -1000);
	middleBounds.setSize(0.0, 2000);

	//Where I will be after move
	PD_rect newBounds = bounds;
	newBounds.topLeft.x += xDir * amountCanMove;

	if (controller.AtFront(this))
	{
		if ((side == PLAYER_LEFT && newBounds.right() < middleBounds.left())
			|| (side == PLAYER_RIGHT && newBounds.left() > middleBounds.right()))
			return true;
		else
		{
			double amountOverlap = getXOverlap(newBounds, middleBounds, xDir);

			//if ((side == PLAYER_LEFT && newBounds.right() < middleBounds.left())
			//|| (side == PLAYER_RIGHT && newBounds.left() > middleBounds.right()))
			//If the overlap wasn't the whole movement, move what wasn't overlapping
			if (amountOverlap < amountCanMove)
			{
				amountCanMove = amountCanMove - amountOverlap;
				return true;
			}

			/*
			if (amountLeft > 0 && (amountCanMove - amountLeft > 0))
			{
				amountCanMove = amountCanMove - amountLeft;
				return true;
			}
			*/

			return false;
		}
	}
	else
	{
		Dude *nextDude = NULL;
		nextDude = controller.GetNextDude(this);

		if (nextDude != NULL)
		{
			PD_rect nextBounds = nextDude->bounds;
			//Calculate overlaps with next guy and center just to be sure
			double amountMiddleOverlap = getXOverlap(newBounds, middleBounds, xDir);
			double amountNextGuyOverlap = getXOverlap(newBounds, nextBounds, xDir);

			if (amountMiddleOverlap <= 0.0 && amountNextGuyOverlap <= 0.0) //as long as we don't overlap with anyone, just return "ok to move"
				return true;
			else
			{
				if (amountMiddleOverlap > amountNextGuyOverlap)
				{
					if (amountMiddleOverlap < amountCanMove)
					{
						amountCanMove = amountCanMove - amountMiddleOverlap;
						return true;
					}
				}
				else
				{
					if (amountNextGuyOverlap < amountCanMove)
					{
						amountCanMove = amountCanMove - amountNextGuyOverlap;
						return true;
					}
				}

				/*
				if (amountLeft > 0 && (amountCanMove - amountLeft > 0))
				{
					amountCanMove = amountCanMove - amountLeft;
					return true;
				}
				*/

				return false;
			}
		}
		else
			return false;
	}
}

void Dude::moveBy(double x, double y)
{
	//Move node
	if (node != NULL)
	{
		double nx, ny;
		node->getDestPosition(nx, ny);
		node->setDestPosition(nx + x, ny + y);

		updateBounds();
	}
}

bool Dude::atDestination()
{
	if (node != NULL)
	{
		return node->isAtDestination();
	}
	return true;
}

void Dude::AttackEnemy(Dude *enemy)
{
	std::stringstream logLine;
	logLine << "AttackEnemy(" << baseResourceName << "): AttackRoll(off:" << stats.offense << " xRoll:" << stats.xRoll << " yRoll:" << enemy->stats.xRoll << " zRoll:" << (stats.xRoll + enemy->stats.xRoll)
		<< ") = " << stats.getAttackRoll(enemy->stats.xRoll) << std::endl;
	logLine << "               DefenseValue(def:" << enemy->stats.defense << " spd:" << enemy->stats.speed << ") = " << enemy->stats.getDefenseValue();
	if (stats.getAttackRoll(enemy->stats.xRoll) > enemy->stats.getDefenseValue())
	{
		enemy->TakeDamage(stats.getDamage());
		logLine << std::endl << "               DamageAmount(dmg:" << stats.damage << " xRoll:" << stats.xRoll << ") = " << stats.getDamage();
	}
	log_info(logLine.str());
}

void Dude::Heartbeat()
{
	double movement_amount = bounds.width() - 5;
	Dude *enemy = controller.GetEnemy(this);

	if (actionCounter < actionTime)
		actionCounter++;

	//Only decide what to do once we are done moving and not if we are dead or invisible (needing to be cleaned up)
	if (appearance != DUDE_INVISIBLE && appearance != DUDE_DEAD && atDestination() && actionCounter == actionTime)
	{
		switch(action)
		{
		//Do nothing
		case DUDE_NOTHING:
			if (canMoveForward(movement_amount)) // && actionCounter == actionTime)
			{
				moveBy(xDir * movement_amount, 0.0);
				StartWalking();
				actionCounter = 0;
			}
			else if (controller.AtFront(this) && enemy != NULL)
			{
				StartFighting();
				//enemy->TakeDamage(stats.attack);
			}
			else if (rand() % 10 < 6 && controller.TryMoveSideways(this)) //actionCounter == actionTime && 
			{
				StartWalking();
			}
			break;
		//Try to attack
		case DUDE_FIGHTING:
			if (controller.AtFront(this) && enemy != NULL) // && actionCounter == actionTime)
			{
				//if (actionCounter == actionTime)
				//{
					AttackEnemy(enemy);
					actionCounter = 0;
				//}
			}
			else if (canMoveForward(movement_amount)) // && actionCounter == actionTime)
			{
				moveBy(xDir * movement_amount, 0.0);
				StartWalking();
			}
			else if (rand() % 10 < 6 && controller.TryMoveSideways(this)) //actionCounter == actionTime && 
			{
				StartWalking();
			}
			else
				StartNothing();
			break;
		//Try to walk forward
		case DUDE_WALKING:
			if (canMoveForward(movement_amount)) // && actionCounter == actionTime)
			{
				moveBy(xDir * movement_amount, 0.0);
			}
			else if (controller.AtFront(this) && enemy != NULL)
			{
				StartFighting();
				//enemy->TakeDamage(stats.attack);
			}
			else if (rand() % 10 < 6 && controller.TryMoveSideways(this)) //actionCounter == actionTime &&
			{
				//Do nothing, it already moved us
			}
			else
				StartNothing();
			break;
		}
	}
	//If we can't do anything right now, but are concious, do nothing for now
	else if (appearance != DUDE_INVISIBLE && appearance != DUDE_DEAD && atDestination())
	{
		StartNothing();
	}

	if (appearance == DUDE_DEAD)
	{
		dyingTime -= 1.0;
		if (dyingTime <= 0)
		{
			appearance = DUDE_INVISIBLE;
		}
	}
}

void Dude::AlignSprite(Sprite* sprite)
{
	if (sprite == NULL)
		return;
	//Get image size
	SDL_Surface* image = sprite->getRenderImage();
	if (image != NULL)
	{
		sprite->setPosition(0, 25.0 - ((double)image->h / 2));
		sprite->setDestPosition(0, 25.0 - ((double)image->h / 2));
	}
	else
	{
		sprite->setPosition(0, 0);
		sprite->setDestPosition(0, 0);
	}
}

SpriteSheet* Dude::CreateSpriteSheet()
{
	std::string subResource = baseResourceName;
	int numFrames = 1;

	if (action == DUDE_WALKING || action == DUDE_FIGHTING)
	{
		int index = baseResourceName.find(".");
		if (index != std::string::npos)
		{
			if (action == DUDE_WALKING)
				subResource = subResource.substr(0, index) + "_movement" + subResource.substr(index);
			else
				subResource = subResource.substr(0, index) + "_fighting" + subResource.substr(index);
			numFrames = 2;
		}
	}
	
	return new SpriteSheet(subResource, true, numFrames);
}

//Builds node tree, based on state
void Dude::InitAppearance()
{
	RenderTarget* oldParent = NULL;

	double x = 0.0, y = 0.0, z = 0.0, dx = 0.0, dy = 0.0;
	bool rel = false;
	if (side == PLAYER_RIGHT)
		xDir = -1.0;
	else
		xDir = 1.0;

	//If previous appearance tree clean it up
	if (node != NULL)
	{
		//Save all important information about node before destroying
		node->getPosition(x, y);
		node->getDestPosition(dx, dy);
		z = node->getZOrder();
		rel = node->getRelativeToParent();

		if (node->getParent() != NULL)
		{
			oldParent = node->getParent();
			node->removeFromParent();
		}

		delete node;
		node = NULL;
	}

	//Set our "node" member to a new top node of this tree
	node = new RenderTarget("dude_node");
	node->setMoveSpeed(90 + 5 * stats.speed);

	//Re-add where it was before recreating
	if (oldParent != NULL)
	{
		node->setPosition(x, y); //set old position for node
		node->setDestPosition(dx, dy);
		node->setZOrder(z);
		node->setRelativeToParent(rel);
		oldParent->addChild(node);
	}
	else
		node->setZOrder(LAYER_MIDDLE);

	SpriteSheet *robotAnimated = CreateSpriteSheet();

	//SpriteSheet *robotAnimated = new SpriteSheet(baseResourceName, true, 1);
	robotAnimated->getIdComp()->id = "dude_avatar";
	robotAnimated->animated = true;
	if (appearance != DUDE_DEAD)
		robotAnimated->setAnimationSpan(rand_double_range(0.25, 0.35));
	else
		robotAnimated->setAnimationSpan(0.12);
	robotAnimated->centered = true;
	robotAnimated->reversed = side == PLAYER_RIGHT;
	robotAnimated->setRelativeToParent(true);
	robotAnimated->OnAnimationCallback = &Dude::staticSpriteAnimationCallback;
	robotAnimated->AnimationCallbackObject = this;
	AlignSprite(robotAnimated);
	robotAnimated->setZOrder(LAYER_FRONT);
	robotAnimated->setVisible(appearance != DUDE_INVISIBLE && appearance != DUDE_DEAD);
	node->addChild(robotAnimated);

	std::string shadowName = "shadow-40x50.png";
	if (robotAnimated->getFrameSize().x >= 75)
		shadowName = "shadow-80.png";

	Sprite* shadowSprite = new Sprite(shadowName, true);
	shadowSprite->centered = true;
	AlignSprite(shadowSprite);
	shadowSprite->setRelativePosition(0, 5);
	double shx , shy;
	shadowSprite->getPosition(shx, shy);
	shadowSprite->setDestPosition(shx, shy);
	shadowSprite->setRelativeToParent(true);
	shadowSprite->setZOrder(LAYER_BACK);
	shadowSprite->setVisible(appearance != DUDE_INVISIBLE && appearance != DUDE_DEAD);
	node->addChild(shadowSprite);

	ProgressBar *lifeMeter = new ProgressBar("mini_life_sm_empty.png", "mini_life_sm_full.png");
	lifeMeter->getIdComp()->id = "life_meter";
	lifeMeter->percentProgress = std::max(0.0, 100.0 * stats.life / stats.getLifeDefault());
	lifeMeter->centered = true;
	lifeMeter->setPosition(0, 27);
	lifeMeter->setRelativeToParent(true);
	lifeMeter->setZOrder(LAYER_FRONT + 100);
	node->addChild(lifeMeter);
	
	if (appearance == DUDE_DAMAGED || appearance == DUDE_DEAD) {
		ParticleSystem *pSys = new ParticleSystem("dude_psys");
		pSys->getIdComp()->addTag(".effect");
		pSys->xrange = 10.0; //50.0;
		pSys->yrange = 10.0; //40.0;
		pSys->velx = 0.0;
		pSys->vely = -20.0; //0.0;
		pSys->velxrange = 20.0; //400.0;
		pSys->velyrange = 10.0; //400.0;
		pSys->gravityConstant = true; //false;
		pSys->life = 0.6;
		pSys->liferange = 0.3; //0.5;
		pSys->maxParticles = 15; //300;
		pSys->relativeParticles = false;//true;		pSys->gravityDir.x = 0.0;
		pSys->gravityDir.y = -10.0; //-200.0;
		pSys->SetupFireTemplate();
		pSys->setPosition(0,10);
		pSys->setZOrder(LAYER_MIDDLE);
		node->addChild(pSys);
		pSys->setRelativeToParent(true);
		pSys->Init();
	}

	//Setup bounding box
	updateBounds();
}

//Handles all memory management of boundsImage, until destructor is called, which does final cleanup
void Dude::updateBounds()
{
	if (node == NULL)
	{
		bounds.setAll(0,0,0,0);
		//Cleanup current bounds Image if set
		if (boundsImage != NULL)
		{	
			SDL_FreeSurface(boundsImage);
			boundsImage = NULL;
		}
	}
	else
	{
		//Get position of node on the field (or in wherever it is)
		double x, y;
		node->getDestPosition(x, y);
		bounds.setTopLeft(x, y);

		//Get dude avatar spritesheet
		SpriteSheet *avatar = (SpriteSheet*)node->findChildWithId("dude_avatar");
		if (avatar != NULL)
		{
			PD_vect2 size = avatar->getFrameSize();
			bounds.setSize(size.x, size.y);
			if (avatar->centered)
				bounds.setTopLeft(bounds.left() - size.x / 2, bounds.top() - size.y / 2);
		}
		else
		{
			bounds.setSize(5, 5); //token size to show the bounds are there
		}

		double zoomx, zoomy;
		SDL_Surface* originalBoundsImage = GameEngine::getResources()->GetImage("bounds.png", true);
		if (originalBoundsImage != NULL)
		{
			
			zoomx = bounds.size.x / originalBoundsImage->w;
			zoomy = bounds.size.y / originalBoundsImage->h;

			//Find previous sprite if possible
			Sprite* boundsSprite = NULL;
			
			boundsSprite = (Sprite*)node->findChildWithId("bounds_sprite");
			if (boundsSprite != NULL)
			{
				boundsSprite->unmanagedImage = NULL; //Clear out old bounds image pointer
			}
			else
			{
				boundsSprite = new Sprite("", true);
				boundsSprite->getIdComp()->id = "bounds_sprite";
			}

			//Cleanup current bounds Image if set
			if (boundsImage != NULL)
			{	
				SDL_FreeSurface(boundsImage);
				boundsImage = NULL;
			}

			//Create bounds image scaled to bounds size
			boundsImage = zoomSurface(originalBoundsImage, zoomx, zoomy, 1);

			//Setup bounds sprite for these bounds
			boundsSprite->setUnmanagedImage(boundsImage);
			boundsSprite->setPosition(0,0);
			boundsSprite->centered = true;
			boundsSprite->setRelativeToParent(true);
			boundsSprite->setZOrder(LAYER_FRONT + 1);
			boundsSprite->setVisible(appearance == DUDE_INVISIBLE);
			AlignSprite(boundsSprite); //align just like avatar
			node->addChild(boundsSprite);
		}
	}
}

PD_vect2 Dude::getBoundsOffset()
{
	PD_vect2 offset;
	//Default to no offset
	offset.x = offset.y = 0;

	if (node != NULL)
	{
		//Get dude avatar spritesheet
		SpriteSheet *avatar = (SpriteSheet*)node->findChildWithId("dude_avatar");
		if (avatar != NULL)
		{
			offset = avatar->getFrameSize();
			offset.x /= -2.0;
			offset.y /= -2.0;
		}
	}
	//Return default
	return offset;
}

void Dude::staticSpriteAnimationCallback(void* obj, SpriteSheet* sprite)
{
	if (obj != NULL)
	{
		Dude *dude = (Dude*)obj;
		dude->SpriteAnimationCallback(sprite);
	}
}

void Dude::SpriteAnimationCallback(SpriteSheet* sprite)
{
	if (appearance != DUDE_DEAD && appearance != DUDE_INVISIBLE)
	{
		//Must be fighting, must be on "hit" frame, and only 1 in 3 times make the sound
		if (action == DUDE_FIGHTING && sprite->frame == 1 && rand() % 3 == 0)
		{
			//Random hit sound
			std::string soundName = "FX Metal Clink 1.wav";
			int i = rand() % 8;
			if (i == 1)
				soundName = "FX Metal Clink 2.wav";
			else if (i == 2)
				soundName = "FX Metal Clink 3.wav";
			else if (i == 3)
				soundName = "FX Metal Clink 4.wav";
			else if (i == 4)
				soundName = "FX Metal Clink 5.wav";
			else if (i == 5)
				soundName = "FX Thwack High.wav";
			else if (i == 6)
				soundName = "FX Thwack Mid.wav";
			else if (i == 7)
				soundName = "FX Thwack Low.wav";

			Mix_Chunk *sound = GameEngine::getResources()->GetSound(soundName);
			if (sound != NULL)
				Mix_PlayChannel(-1, sound, 0);
		}
		else if (action == DUDE_WALKING && sprite->frame == 0 && rand() % 16 == 0)
		{
			//Random hit sound
			std::string soundName = "FX Hit 1.wav";
			if (rand() % 2 == 1)
				soundName = "FX Hit 2.wav";

			Mix_Chunk *sound = GameEngine::getResources()->GetSound(soundName);
			if (sound != NULL)
				Mix_PlayChannel(-1, sound, 0);
		}
	}
	else if (appearance == DUDE_DEAD && node != NULL)
	{
		RenderTarget *avatar = node->findChildWithId("dude_avatar");
		if (avatar != NULL)
		{
			avatar->setVisible( !avatar->getVisible() );
		}
	}
}


bool Dude::TakeDamage(Uint16 amount)
{
	bool needsUpdate = false;

	if (appearance != DUDE_DEAD && appearance != DUDE_INVISIBLE)
	{
		stats.life -= amount;

		if (node != NULL)
		{
			ProgressBar *life = dynamic_cast<ProgressBar*>(node->findChildWithId("life_meter"));
			if (life != NULL)
			{
				life->percentProgress = std::max(0.0, 100.0 * stats.life / stats.getLifeDefault());
			}
		}

		/*
		if (stats.life <= 0)
		{
			appearance = DUDE_DEAD;
			dyingTime = 2.0;
			needsUpdate = true;
		} */
		if (stats.life < 5 && appearance == DUDE_NORMAL)
		{
			appearance = DUDE_DAMAGED;
			needsUpdate = true;
		}

		if (needsUpdate)
			InitAppearance();

	}

	return false;
}

void Dude::StartNothing()
{
	if (action != DUDE_NOTHING)
	{
		action = DUDE_NOTHING;
		InitAppearance();
	}
}

void Dude::StartWalking()
{
	if (action != DUDE_WALKING)
	{
		action = DUDE_WALKING;
		InitAppearance();
	}
}

void Dude::StartFighting()
{
	if (action != DUDE_FIGHTING)
	{
		action = DUDE_FIGHTING;
		InitAppearance();
	}
}

void Dude::StartDying()
{
	if (appearance != DUDE_DEAD && appearance != DUDE_INVISIBLE)
	{
		appearance = DUDE_DEAD;
		dyingTime = 5.0;
		InitAppearance();
	}
}

double Dude::getZ()
{
	if (node != NULL)
		return node->getZOrder();
	return 0.0;
}

void Dude::setZ(double z)
{
	if (node != NULL)
	{
		node->setZOrder(z);
	}
}